/*
 * Copyright (c) 2023, Alibaba Group Holding Limited;
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Author's Email: metabeyond@outlook.com
 * Author's Github: https://github.com/refvalue/
 * Description: this source file contains an implementation of a compile-time
 * meta-string that can be utilized as a non-type parameter of a template.
 */
#pragma once

#include <algorithm>
#include <array>
#include <cstddef>
#if __has_include(<span>)
#    include <compare>
#    include <concepts>
#    include <span>
#endif
#include <string_view>
#include <utility>

namespace refvalue {
template <std::size_t N>
struct meta_string {
    std::array<char, N + 1> elements_;

    constexpr meta_string() noexcept : elements_{} {}

    constexpr meta_string(const char (&data)[N + 1]) noexcept {
        for (size_t i = 0; i < N + 1; i++)
            elements_[i] = data[i];
    }

#if __has_include(<span>)
    template <std::size_t... Ns>
    constexpr meta_string(std::span<const char, Ns>... data) noexcept
        : elements_{} {
        auto iter = elements_.begin();

        ((iter = std::copy(data.begin(), data.end(), iter)), ...);
    }
#endif

    template <std::size_t... Ns>
    constexpr meta_string(const meta_string<Ns> &...data) noexcept
        : elements_{} {
        auto iter = elements_.begin();

        ((iter = std::copy(data.begin(), data.end(), iter)), ...);
    }

#if __has_include(<span>)
    template <std::same_as<char>... Ts>
    constexpr meta_string(Ts... chars) noexcept requires(sizeof...(Ts) == N)
        : elements_{chars...} {}
#endif

    constexpr char &operator[](std::size_t index) noexcept {
        return elements_[index];
    }

    constexpr const char &operator[](std::size_t index) const noexcept {
        return elements_[index];
    }

    constexpr operator std::string_view() const noexcept {
        return std::string_view{elements_.data(), size()};
    }

    constexpr bool empty() const noexcept { return size() == 0; }

    constexpr std::size_t size() const noexcept { return N; }

    constexpr char &front() noexcept { return elements_.front(); }

    constexpr const char &front() const noexcept { return elements_.front(); }

    constexpr char &back() noexcept { return elements_[size() - 1]; }

    constexpr const char &back() const noexcept {
        return elements_[size() - 1];
    }

    constexpr auto begin() noexcept { return elements_.begin(); }

    constexpr auto begin() const noexcept { return elements_.begin(); }

    constexpr auto end() noexcept { return elements_.begin() + size(); }

    constexpr auto end() const noexcept { return elements_.begin() + size(); }

    constexpr char *data() noexcept { return elements_.data(); }

    constexpr const char *data() const noexcept { return elements_.data(); };

    constexpr const char *c_str() const noexcept { return elements_.data(); }

    constexpr bool contains(char c) const noexcept {
        return std::find(begin(), end(), c) != end();
    }

    constexpr bool contains(std::string_view str) const noexcept {
        return str.size() <= size()
                   ? std::search(begin(), end(), str.begin(), str.end())
                         != end()
                   : false;
    }

    static constexpr size_t substr_len(size_t pos, size_t count) {
        if (pos >= N) {
            return 0;
        } else if (count == std::string_view::npos || pos + count > N) {
            return N - pos;
        } else {
            return count;
        }
    }

    template <size_t pos, size_t count = std::string_view::npos>
    constexpr meta_string<substr_len(pos, count)> substr() const noexcept {
        constexpr size_t n = substr_len(pos, count);

        meta_string<n> result;
        for (std::size_t i = 0; i < n; ++i) {
            result[i] = elements_[pos + i];
        }
        return result;
    }

    constexpr size_t rfind(char c) const noexcept {
        return std::string_view(*this).rfind(c);
    }

    constexpr size_t find(char c) const noexcept {
        return std::string_view(*this).find(c);
    }
};

template <std::size_t N>
meta_string(const char (&)[N]) -> meta_string<N - 1>;

#if __has_include(<span>)
template <std::size_t... Ns>
meta_string(std::span<const char, Ns>...) -> meta_string<(Ns + ...)>;
#endif

template <std::size_t... Ns>
meta_string(const meta_string<Ns> &...) -> meta_string<(Ns + ...)>;

#if __has_include(<span>)
template <std::same_as<char>... Ts>
meta_string(Ts...) -> meta_string<sizeof...(Ts)>;
#endif

#if __has_include(<span>)
template <std::size_t M, std::size_t N>
constexpr auto operator<=>(const meta_string<M> &left,
                           const meta_string<N> &right) noexcept {
    return static_cast<std::string_view>(left).compare(
               static_cast<std::string_view>(right))
           <=> 0;
}
#endif

template <std::size_t M, std::size_t N>
constexpr bool operator==(const meta_string<M> &left,
                          const meta_string<N> &right) noexcept {
    return static_cast<std::string_view>(left)
           == static_cast<std::string_view>(right);
}

template <std::size_t M, std::size_t N>
constexpr bool operator==(const meta_string<M> &left,
                          const char (&right)[N]) noexcept {
    return static_cast<std::string_view>(left)
           == static_cast<std::string_view>(meta_string{right});
}

template <std::size_t M, std::size_t N>
constexpr auto operator+(const meta_string<M> &left,
                         const meta_string<N> &right) noexcept {
    return meta_string{left, right};
}

template <std::size_t M, std::size_t N>
constexpr auto operator+(const meta_string<M> &left,
                         const char (&right)[N]) noexcept {
    meta_string<M + N - 1> s;
    for (size_t i = 0; i < M; ++i)
        s[i] = left[i];
    for (size_t i = 0; i < N; ++i)
        s[M + i] = right[i];
    return s;
}

template <std::size_t M, std::size_t N>
constexpr auto operator+(const char (&left)[M],
                         const meta_string<N> &right) noexcept {
    meta_string<M + N - 1> s;
    for (size_t i = 0; i < M - 1; ++i)
        s[i] = left[i];
    for (size_t i = 0; i < N; ++i)
        s[M + i - 1] = right[i];
    return s;
}

#if __has_include(<span>)
template <meta_string S, meta_string Delim>
struct split_of {
    static constexpr auto value = [] {
        constexpr std::string_view view{S};
        constexpr auto group_count
            = std::count_if(
                  S.begin(), S.end(), [](char c) { return Delim.contains(c); })
              + 1;
        std::array<std::string_view, group_count> result{};

        auto iter = result.begin();

        for (std::size_t start_index = 0, end_index = view.find_first_of(Delim);
             ;
             start_index = end_index + 1,
                         end_index = view.find_first_of(Delim, start_index)) {
            *(iter++) = view.substr(start_index, end_index - start_index);

            if (end_index == std::string_view::npos) {
                break;
            }
        }

        return result;
    }();
};

template <meta_string S, meta_string Delim>
inline constexpr auto &&split_of_v = split_of<S, Delim>::value;

template <meta_string S, meta_string Delim>
struct split {
    static constexpr std::string_view view{S};
    static constexpr auto value = [] {
        constexpr auto group_count = [] {
            std::size_t count{};
            std::size_t index{};

            while ((index = view.find(Delim, index))
                   != std::string_view::npos) {
                count++;
                index += Delim.size();
            }

            return count + 1;
        }();
        std::array<std::string_view, group_count> result{};

        auto iter = result.begin();

        for (std::size_t start_index = 0, end_index = view.find(Delim);;
             start_index = end_index + Delim.size(),
                         end_index = view.find(Delim, start_index)) {
            *(iter++) = view.substr(start_index, end_index - start_index);

            if (end_index == std::string_view::npos) {
                break;
            }
        }

        return result;
    }();
};

template <meta_string S, meta_string Delim>
inline constexpr auto &&split_v = split<S, Delim>::value;

template <meta_string S, char C>
struct remove_char {
    static constexpr auto value = [] {
        struct removal_metadata {
            decltype(S) result;
            std::size_t actual_size;
        };

        constexpr auto metadata = [] {
            auto result = S;
            auto removal_end = std::remove(result.begin(), result.end(), C);

            return removal_metadata{.result{std::move(result)},
                                    .actual_size{static_cast<std::size_t>(
                                        removal_end - result.begin())}};
        }();

        meta_string<metadata.actual_size> result;

        std::copy(metadata.result.begin(),
                  metadata.result.begin() + metadata.actual_size,
                  result.begin());

        return result;
    }();
};

template <meta_string S, char C>
inline constexpr auto &&remove_char_v = remove_char<S, C>::value;

template <meta_string S, meta_string Removal>
struct remove {
    static constexpr auto groups = split_v<S, Removal>;
    static constexpr auto value = [] {
        return []<std::size_t... Is>(std::index_sequence<Is...>) {
            return meta_string{std::span<const char, groups[Is].size()>{
                groups[Is].data(), groups[Is].size()}...};
        }
        (std::make_index_sequence<groups.size()>{});
    }();
};

template <meta_string S, meta_string Removal>
inline constexpr auto &&remove_v = remove<S, Removal>::value;
#endif
} // namespace refvalue
